package com.oracle.coherence.patterns.security.accesscontrol;

import com.oracle.coherence.patterns.security.KrbTicketPrincipal;
import com.oracle.coherence.patterns.security.extend.CachePermission;
import com.oracle.coherence.patterns.security.extend.InvocationPermission;
import com.oracle.coherence.patterns.security.management.JmxPermission;
import com.tangosol.net.ClusterPermission;
import com.tangosol.run.xml.XmlElement;

import java.security.Permission;
import java.security.Permissions;
import java.security.Principal;
import java.util.*;

/**
 * An implementation of {@link PermissionsChecker} that uses XML to set the
 * permissions.
 * <p/>
 * The XML permissions are provided in the form of an {@link XmlElement} instance.
 *
 * @author Jonathan Knight
 */
public class XmlPermissionsChecker extends BasePermissionsChecker {
    private enum PermissionType {
        CLUSTER, CACHE, JMX, INVOCATION
    }

    /**
     * The permissions that will be used to authorise access
     */
    private XmlElement xmlPermissions;

    private Map<String, Permissions> permissions = new HashMap<String, Permissions>();

    public XmlPermissionsChecker() {
        this(null, true);
    }

    public XmlPermissionsChecker(boolean allowLocal) {
        this(null, allowLocal);
    }

    public XmlPermissionsChecker(XmlElement xmlPermissions, boolean allowLocal) {
        super(allowLocal);
        setXmlPermissions(xmlPermissions);
    }

    public XmlElement getXmlPermissions() {
        return xmlPermissions;
    }

    protected void setXmlPermissions(XmlElement xmlPermissions) {
        this.xmlPermissions = xmlPermissions;
        this.permissions = loadPermissions(xmlPermissions);
    }

    Map<String, Permissions> getCachedPermissions() {
        return permissions;
    }

    void setCachedPermissions(Map<String, Permissions> permissions) {
        this.permissions = permissions;
    }

    Map<String, Permissions> loadPermissions(XmlElement xmlPermissions) {
        Map<String, Permissions> permissionsMap = new HashMap<String, Permissions>();
        if (xmlPermissions != null) {
            Iterator grants = xmlPermissions.getElements("grant");
            while (grants.hasNext()) {
                XmlElement grant = (XmlElement)grants.next();
                Permissions permits = processPermissions(grant);

                if (permits.elements().hasMoreElements()) {
                    Iterator principals = grant.getElements("principal");
                    if (principals.hasNext()) {
                        while (principals.hasNext()) {
                            XmlElement principal = (XmlElement)principals.next();
                            String name = principal.getSafeElement("name").getString();
                            if (name != null && name.length() > 0) {
                                Permissions current = permissionsMap.get(name);
                                if (current == null) {
                                    current = new Permissions();
                                }
                                Enumeration<Permission> enumeration = permits.elements();
                                while (enumeration.hasMoreElements()) {
                                    current.add(enumeration.nextElement());
                                }
                                permissionsMap.put(name.toLowerCase(), current);
                            }
                        }
                    }

                    Iterator groups = grant.getElements("group");
                    if (groups.hasNext()) {
                        XmlElement group = (XmlElement)groups.next();
                        String groupName = group.getString();
                        if (groupName != null && groupName.length() > 0) {
                            Permissions current = permissionsMap.get(groupName);
                            if (current == null) {
                                current = new Permissions();
                            }
                            Enumeration<Permission> enumeration = permits.elements();
                            while (enumeration.hasMoreElements()) {
                                current.add(enumeration.nextElement());
                            }
                            permissionsMap.put(groupName, current);
                        }
                    }
                }
            }
        }
        return permissionsMap;
    }

    Permissions processPermissions(XmlElement grant) {
        Permissions permits = new Permissions();
        processPermissions(grant.getElements("permission"), permits, PermissionType.CLUSTER);
        processPermissions(grant.getElements("cluster-permission"), permits, PermissionType.CLUSTER);
        processPermissions(grant.getElements("cache-permission"), permits, PermissionType.CACHE);
        processPermissions(grant.getElements("invocation-permission"), permits, PermissionType.INVOCATION);
        processPermissions(grant.getElements("jmx-permission"), permits, PermissionType.JMX);
        return permits;
    }

    Permissions getPermissionsForPrincipal(Principal principal) {
        return getPermissionsForPrincipal(principal, this.permissions);
    }

    Permissions getPermissionsForPrincipal(Principal principal, Map<String, Permissions> permissions) {
        Permissions principalPermissions = new Permissions();

        if (principal instanceof KrbTicketPrincipal) {
            Set<String> keys = new HashSet<String>();
            keys.add(String.valueOf(principal.getName()).toLowerCase());
            keys.addAll(((KrbTicketPrincipal)principal).getGroups());
            
            Set<String> permissionKeys = new HashSet<String>(permissions.keySet());
            permissionKeys.retainAll(keys);
            for (String key : permissionKeys) {
                Permissions permits = permissions.get(key);
                Enumeration<Permission> permEnum = permits.elements();
                while (permEnum.hasMoreElements()) {
                    principalPermissions.add(permEnum.nextElement());
                }
            }
        }
        return principalPermissions;
    }

    public void checkPermissionForPrincipal(Permission permission, Principal principal) {
        Permissions permits = getPermissionsForPrincipal(principal);
        if (permits == null || !permits.implies(permission)) {
            throw new SecurityException("Principal " + principal.getName()
                    + " is not authorised for permission " + permission);
        }
    }

    void processPermissions(Iterator iter, Permissions permits, PermissionType type) {
        while (iter.hasNext()) {
            XmlElement xmlPermission = (XmlElement) iter.next();
            String target = xmlPermission.getSafeElement("target").getString();
            String action = xmlPermission.getSafeElement("action").getString();

            Permission permit;
            switch (type) {
                case CACHE:
                    permit = new CachePermission(target, action);
                    permits.add(permit);
                    break;
                case CLUSTER:
                    permit = new ClusterPermission(target, action);
                    permits.add(permit);
                    break;
                case JMX:
                    permit = new JmxPermission(target, action);
                    permits.add(permit);
                    break;
                case INVOCATION:
                    permit = new InvocationPermission(target, action);
                    permits.add(permit);
                    break;
            }

        }
    }

}
